package com.gitee.wsl.lang.ubytearray

import com.gitee.wsl.ext.bitcode.bitLength
import com.gitee.wsl.ext.bitcode.shl
import com.gitee.wsl.ext.bitcode.shr
import com.gitee.wsl.ext.number.throughBuffer
import com.gitee.wsl.ext.number.toTwosComplementByteArray
import com.gitee.wsl.io.wrapInUnsafeSource
import kotlinx.io.Buffer
import kotlinx.io.Sink
import kotlinx.io.Source
import kotlinx.io.UnsafeIoApi
import kotlinx.io.readByteArray
import kotlinx.io.readUByte
import kotlinx.io.writeUByte
import kotlin.experimental.or
import kotlin.jvm.JvmInline

@OptIn(ExperimentalUnsignedTypes::class)
@JvmInline
value class VarUInt
private constructor(val words: UByteArray) {

    init {
        check(!words.isEmpty())
        check((words.size == 1) || (words.first() != 0x00u.toUByte()))
    }

    @Suppress("NOTHING_TO_INLINE")
    inline fun isEqualTo(other: VarUInt) = (words contentEquals other.words)

    val bytes get() = words.copyOf()

    override fun toString() = words.iterator().toDecimalString()
    fun toHexString() = StringBuilder().apply {
        words.forEachIndexed { i, it ->
            append(
                it.toString(16).run { if (i > 0 && length < 2) "0$this" else this })
        }
    }.toString()

    infix fun and(other: VarUInt): VarUInt {
        val (shorter, longer) = if (other.words.size < words.size) other to this else this to other
        val diff = longer.words.size - shorter.words.size
        return constructFromUntrimmed(UByteArray(shorter.words.size) {
            shorter.words[it] and longer.words[it + diff]
        }, isOwned = true)
    }

    infix fun or(other: VarUInt): VarUInt {
        val (shorter, longer) = if (other.words.size < words.size) other to this else this to other
        val diff = longer.words.size - shorter.words.size
        return VarUInt(UByteArray(longer.words.size) {
            if (it >= diff) shorter.words[it - diff] or longer.words[it]
            else longer.words[it]
        })
    }

    infix fun xor(other: VarUInt): VarUInt {
        val (shorter, longer) = if (other.words.size < words.size) other to this else this to other
        val diff = longer.words.size - shorter.words.size
        return constructFromUntrimmed(UByteArray(longer.words.size) {
            if (it >= diff) shorter.words[it - diff] xor longer.words[it]
            else longer.words[it]
        }, isOwned = true)
    }

    infix fun shl(offset: Int): VarUInt {
        require(offset >= 0) { "offset must be non-negative: $offset" }
        if ((offset == 0) || this.isZero()) return this

        val highWordBits = 8-(offset % 8)
        if (highWordBits == 8) return VarUInt(words.copyOf(words.size + (offset/8)))

        val newSize = words.size + (offset/8) + 1

        return constructFromUntrimmed(UByteArray(newSize) { i ->
            when {
                i == 0 -> words[i] shr highWordBits
                i < words.size -> combine(words[i-1], words[i], highWordBits)
                i == words.size -> words[i-1] shl 8-highWordBits
                else -> 0x00u
            }
        }, isOwned = true)
    }

    infix fun shr(offset: Int): VarUInt {
        require(offset >= 0) { "offset must be non-negative: $offset" }
        if ((offset == 0) || this.isZero()) return this

        val newSize = words.size - (offset / 8)
        if (newSize <= 0) return ZERO

        val highWordBits = offset % 8
        if (highWordBits == 0) return VarUInt(words.copyOfRange(0, newSize))
        return constructFromUntrimmed(UByteArray(newSize) { i -> when {
            i > 0 -> combine(words[i-1], words[i], highWordBits)
            else -> words[i] shr highWordBits
        }}, true)
    }

    fun toAsn1VarInt(): ByteArray = throughBuffer { it.writeAsn1VarInt(this) }

    fun isZero(): Boolean = (words.first() == 0.toUByte()) //always trimmed, so it is enough to inspect the first byte

    fun bitLength(): Int = 8*(words.size-1) + words.first().bitLength

    fun inv(): VarUInt = constructFromUntrimmed(UByteArray(words.size) { words[it].inv() }, true)


    operator fun compareTo(byte: UByte): Int = if (words.size > 1) 1 else words.last().compareTo(byte)


    /**
     * @throws IllegalArgumentException if the number is too large
     */
    @Throws(IllegalArgumentException::class)
    fun shortValue(): Int =
        if(words.size>2) throw IllegalArgumentException("Number too large!")
        else if (words.size > 1) words.last().toInt() and (words[words.lastIndex - 1].toInt() shl 8)
        else words.last().toInt()


    companion object {
        private const val UVARINT_SINGLEBYTE_MAXVALUE_UBYTE: UByte = 0x80u
        internal const val UVARINT_SINGLEBYTE_MAXVALUE: Byte = 0x80.toByte()
        internal const val UVARINT_MASK_UBYTE: UByte = 0x7Fu
        private val REGEX_BASE10 = Regex("[0-9]+")
        private val REGEX_ZERO = Regex("0*")
        val ZERO = VarUInt(ubyteArrayOf(0x00u))

        private fun constructFromUntrimmed(untrimmed: UByteArray, isOwned: Boolean): VarUInt {
            val i = untrimmed.indexOfFirst { it != 0x00u.toUByte() }
            return when {
                (i == -1) -> ZERO
                (i == 0 && isOwned) -> VarUInt(untrimmed)
                else -> VarUInt(untrimmed.copyOfRange(i, untrimmed.size))
            }
        }

        operator fun invoke(uByte: UByte = 0x00u) = constructFromUntrimmed(ubyteArrayOf(uByte), true)
        operator fun invoke(value: String) = constructFromUntrimmed(value.parseAsBase10().toUByteArray(), true)
        operator fun invoke(ubyteArray: UByteArray) = constructFromUntrimmed(ubyteArray, false)
        operator fun invoke(byteArray: ByteArray) = constructFromUntrimmed(byteArray.asUByteArray(), false)
        operator fun invoke(uLong: ULong) = constructFromUntrimmed(uLong.toTwosComplementByteArray().asUByteArray(), true)
        operator fun invoke(uInt: UInt) = constructFromUntrimmed(uInt.toTwosComplementByteArray().asUByteArray(), true)

        internal fun constructUnsafe(ownedArray: UByteArray) = constructFromUntrimmed(ownedArray, true)

        internal fun Sink.writeAsn1VarInt(number: VarUInt): Int {
            if (number.isZero()) {
                writeByte(0)
                return 1
            }
            val numBytes = (number.bitLength() + 6) / 7 // division rounding up

            (numBytes - 1).downTo(0).forEach { byteIndex ->
                writeByte(
                    ((number shr (byteIndex * 7)).words.last() and UVARINT_MASK_UBYTE).toByte() or
                            (if (byteIndex > 0) UVARINT_SINGLEBYTE_MAXVALUE else 0)
                )
            }
            return numBytes
        }

        private fun MutableList<Char>.divRem(d: Int) : Pair<MutableList<Char>, Int> {
            val result = mutableListOf<Char>()
            var residue = 0
            // result * 256 + residue == (string[0..i])
            for (char in this) {
                val currentDigit = residue * 10 + char.digitToInt()
                result.add((currentDigit / d).digitToChar()) // Append the quotient
                residue = currentDigit % d // Update remainder
            }
            result.apply { while (isNotEmpty() && first() == '0') removeFirst() }
            return Pair(result, residue)
        }

        @OptIn(ExperimentalUnsignedTypes::class)
        private fun String.parseAsBase10(): UByteArray {
            if (!matches(REGEX_BASE10)) throw Exception("Illegal input!")
            if (matches(REGEX_ZERO)) return ubyteArrayOf(0x00u)
            var currentValue = toMutableList()
            val byteList = mutableListOf<UByte>()
            while ((currentValue.size > 1) || (currentValue.size == 1 && currentValue.first() != '0')) {
                currentValue.divRem(256).let { (newValue, rem) ->
                    currentValue = newValue
                    byteList.add(rem.toUByte())
                }
            }
            return UByteArray(byteList.size) { byteList[byteList.size-it-1] }
        }


        private fun Iterator<UByte>.toDecimalString(): String {
            // Initialize the result to hold the base-10 value
            var decimalResult = mutableListOf('0')

            // Process each byte in the base-256 array
            for (byte in this) {
                // Convert byte to an integer (unsigned)
                val value = byte.toInt() and 0xFF

                // Multiply the current decimal result by 256
                decimalResult = decimalResult.times256()

                // Add the new value
                decimalResult = decimalResult decimalPlus value.toString().toList()
            }

            return decimalResult.joinToString(separator = "")
        }

        // Function to multiply a large base-10 number (as a string) by 256
        private fun List<Char>.times256(): MutableList<Char> {
            var carry = 0
            val result = StringBuilder()

            for (digit in asReversed()) {
                val prod = digit.digitToInt() * 256 + carry
                result.append(prod % 10)
                carry = prod / 10
            }

            // Add remaining carry
            while (carry > 0) {
                result.append(carry % 10)
                carry /= 10
            }

            return result.reverse().toMutableList()
        }

        // Function to add two large base-10 numbers (as strings)
        internal infix fun List<Char>.decimalPlus(num2: List<Char>): MutableList<Char> {
            val result = StringBuilder()
            var carry = 0

            val (shorter, longer) = (if (size < num2.size) this to num2
            else num2 to this).let { (a, b) -> a.asReversed() to b.asReversed() }

            for (i in longer.indices) {
                val sum = (if (shorter.size > i) shorter[i].digitToInt() else 0) + longer[i].digitToInt() + carry
                result.append(sum % 10)
                carry = sum / 10
            }

            // Add remaining carry
            while (carry > 0) {
                result.append(carry % 10)
                carry /= 10
            }

            return result.reverse().toMutableList()
        }

        @OptIn(UnsafeIoApi::class)
        internal fun ByteArray.decodeAsn1VarBigUInt() = wrapInUnsafeSource().decodeAsn1VarBigUInt().first

        internal fun Source.decodeAsn1VarBigUInt(): Pair<VarUInt, ByteArray> {
            val accumulator = Buffer()
            var result = VarUInt()
            val mask = 0x7Fu.toUByte()
            while (!exhausted()) {
                val current = readUByte()
                accumulator.writeUByte(current)
                result = VarUInt(current and mask) or (result shl 7)
                if (current < 0x80.toUByte()) break
            }
            return result to accumulator.readByteArray()
        }
    }
}

internal fun combine(highByte: UByte, lowByte: UByte, highBits: Int) =
    ((highByte.toUInt() shl (8-highBits)) or (lowByte.toUInt() shr highBits)).toUByte()

